﻿using System;
using Nuwanda.Common;
using System.Net;
using System.IO;
using System.Xml;
using System.Reflection;
using Nuwanda.TfsSpotlight.Shell.Properties;
using Nuwanda.TfsSpotlight.Shell.Services.Interfaces;
using StructureMap;
using System.Security.Permissions;
using System.ComponentModel;

namespace Nuwanda.TfsSpotlight.Shell.Services
{
    [Author("Bartłomiej Legiędź")]
    [Pluggable("UpdateService")]
    public class UpdateService : IUpdateService
    {
        private readonly string _updateSource = @"http://nuwanda.net/TfsSpotlight/Update.xml";
        private readonly string _packageTempFileName = "UpdatePackage.zip";

        private ILogger Logger { get; set; }
        private IMonitService MonitService { get; set; }
        private IAsyncService AsyncService { get; set; }
        private IApplicationController ApplicationController { get; set; }

        public UpdateService(ILogger logger, IMonitService monitService, IAsyncService asyncService, IApplicationController applicationController)
        {
            this.Logger = logger;
            this.MonitService = monitService;
            this.AsyncService = asyncService;
            this.ApplicationController = applicationController;
        }

        public void CheckForUpdates()
        {
            this.AsyncService.QueueInPool(
                delegate
                    {
                        this.Logger.WriteActivity(Messages.LogCheckingForUpdates);

                        try
                        {
                            var remoteVersion = GetRemoteVersionInfo();
                            var currentVersion = GetCurrentVersionInfo();

                            this.Logger.WriteInformation(String.Format(Messages.LogCurrentVersion,
                                                                       currentVersion.Version));
                            this.Logger.WriteInformation(String.Format(Messages.LogRemoteVersion, remoteVersion.Version));

                            if (remoteVersion > currentVersion)
                            {
                                this.MonitService.ShowMonitMessage(
                                    String.Format(Messages.MsgNewUpdateFound, currentVersion.Version,
                                                  remoteVersion.Version),
                                    this.InstallUpdate);
                            }
                            else
                            {
                                this.Logger.WriteInformation(Messages.MsgRunningLatsetVersion);
                            }
                        }
                        catch (WebException we)
                        {
                            this.Logger.WriteError(Messages.MsgUpdateManifestNotFound);
                            this.Logger.WriteException(we);
                        }
                        catch (Exception e)
                        {
                            this.Logger.WriteException(e);
                        }
                    });
        }

        [FileIOPermission(SecurityAction.LinkDemand, AllLocalFiles=FileIOPermissionAccess.AllAccess)]
        public void InstallUpdate()
        {
            this.AsyncService.QueueInPool(
                delegate
                    {
                        this.Logger.WriteActivity(Messages.LogUpdatingApplication);

                        try
                        {
                            var remoteVersion = GetRemoteVersionInfo();

                            this.Logger.WriteActivity(Messages.LogDownloadingPackage);

                            using (var webClient = new WebClient())
                            {
                                this.MonitService.ShowMonitMessage(Messages.MsgDownloadingUpdate, null);
                                webClient.DownloadFileCompleted += HandleUpdatePackageDownloadCompleted;
                                webClient.DownloadFileAsync(new Uri(remoteVersion.Package), this._packageTempFileName);
                            }
                        }
                        catch (Exception e)
                        {
                            this.Logger.WriteException(e);
                            File.Delete(this._packageTempFileName);
                            this.MonitService.ShowMonitMessage(Messages.MsgUpdateError, null);
                        }
                    });
        }

        private void HandleUpdatePackageDownloadCompleted(object sender, AsyncCompletedEventArgs e)
        {
            if(e.Error != null)
            {
                this.Logger.WriteException(e.Error);
                File.Delete(this._packageTempFileName);
                this.MonitService.ShowMonitMessage(Messages.MsgUpdateError, null);
            }
            else
            {
                this.Logger.WriteActivity(Messages.LogDownloadingComplete);
                this.MonitService.ShowMonitMessage(Messages.MsgApplicationUpdated, this.RestartApplication);
            }
        }

        private void RestartApplication()
        {
            this.ApplicationController.RestartApplication();
        }

        private VersionInfo GetRemoteVersionInfo()
        {
            var tempManifestFileName = Path.GetTempFileName();

            using (var webClient = new WebClient())
            {
                webClient.DownloadFile(this._updateSource, tempManifestFileName);
            }

            var xmlDocument = new XmlDocument();
            xmlDocument.Load(tempManifestFileName);

            if (xmlDocument.DocumentElement == null) return null;

            var latestVersion = xmlDocument.DocumentElement.SelectSingleNode(@"/Update/LatestVersion").InnerText;
            var package = xmlDocument.DocumentElement.SelectSingleNode(@"/Update/Package").InnerText;
            var reference = xmlDocument.DocumentElement.SelectSingleNode(@"/Update/Reference").InnerText;

            File.Delete(tempManifestFileName);

            return new VersionInfo(latestVersion, package, reference);
        }

        private static VersionInfo GetCurrentVersionInfo()
        {
            var mainAssembly = Assembly.GetEntryAssembly();

            if (mainAssembly == null)
            {
                return new VersionInfo("10000.0.0.0", String.Empty, String.Empty);
            }

            var mainAssemblyName = mainAssembly.GetName();
            
            return new VersionInfo(mainAssemblyName.Version);
        }
    }
}